from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db import models
from staff.models import Employee


class Candidate(models.Model):
    GENDER_CHOICES = [
        ('男', '男'),
        ('女', '女')
    ]
    DEGREE_CHOICES = [
        ('专科', '专科'),
        ('本科', '本科'),
        ('硕士', '硕士'),
        ('博士', '博士'),
    ]
    INTERVIEW_CHOICES = [
        (0, '待面试'),
        (1, '通过'),
        (2, '未通过')
    ]
    FINAL_CHOICES = [
        (0, '待处理'),
        (1, '已通过'),
        (2, '未通过')
    ]

    # 基本信息
    name = models.CharField('姓名', max_length=100, db_index=True)
    gender = models.CharField('性别', max_length=2, choices=GENDER_CHOICES)
    age = models.PositiveIntegerField('年龄')
    degree = models.CharField('学历', max_length=10, choices=DEGREE_CHOICES)
    phone = models.CharField(
        '联系电话',
        max_length=11,
        validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确')]
    )
    email = models.EmailField('邮箱', null=True, blank=True)
    apply_department = models.CharField('应聘部门', max_length=20, db_index=True)
    apply_position = models.CharField('应聘职位', max_length=20)

    # 一面
    first_interviewer = models.ForeignKey(
        Employee, on_delete=models.PROTECT,
        related_name='first_interview_candidates',
        verbose_name='一面面试官',
        limit_choices_to={'status': '在职'},
        null=True, blank=True)
    first_interview_result = models.IntegerField(
        '一面结果', choices=INTERVIEW_CHOICES, default=0)
    first_interview_date = models.DateField('一面时间', null=True, blank=True)
    first_unpass_reason = models.CharField(
        '一面未通过原因', max_length=200, null=True, blank=True)

    #  二面
    second_interviewer = models.ForeignKey(
        Employee, on_delete=models.PROTECT,
        related_name='second_interview_candidates',
        verbose_name='二面面试官',
        limit_choices_to={'status': '在职'},
        null=True, blank=True)
    second_interview_result = models.IntegerField(
        '二面结果', choices=INTERVIEW_CHOICES, default=0)
    second_interview_date = models.DateField('二面时间', null=True, blank=True)
    second_unpass_reason = models.CharField(
        '二面未通过原因', max_length=200, null=True, blank=True)

    # 三面
    third_interviewer = models.ForeignKey(
        Employee, on_delete=models.PROTECT,
        related_name='third_interview_candidates',
        verbose_name='三面面试官',
        limit_choices_to={'status': '在职'},
        null=True, blank=True)
    third_interview_result = models.IntegerField(
        '三面结果', choices=INTERVIEW_CHOICES, default=0)
    third_interview_date = models.DateField('三面时间', null=True, blank=True)
    third_unpass_reason = models.CharField(
        '三面未通过原因', max_length=200, null=True, blank=True)

    #  最终状态
    final_status = models.IntegerField(
        '最终状态', choices=FINAL_CHOICES, default=0, db_index=True)

    # 时间戳
    create_time = models.DateTimeField('创建时间', auto_now_add=True)
    update_time = models.DateTimeField('更新时间', auto_now=True)

    #  CheckConstraint 是用来检查字段约束的方法:check参数为需要检查的字段   name为唯一约束名
    #  一面为 1 则通过 则二面状态为 0 （待安排） 二面的面试官、面试时间、原因等数据可以为空，符合业务场景 （一面通过 二面待安排）
    #  如果一面为 2 则代表未通过 二面面试官、面试时间、原因等数据必须为空，数据不可插入，（防止脏数据：一面未通过，但有二面的数据）
    # 二面的逻辑同上，三面则不同，二面未通过或通过都允许为空（符合部分岗位只需要二面的业务场景）
    class Meta:
        constraints = [
            models.CheckConstraint(
                check=(
                        models.Q(first_interview_result=1) |  # 一面的结果为 1 （通过）
                        (models.Q(second_interviewer__isnull=True) &  # 二面面试官允许为空
                         models.Q(second_interview_date__isnull=True) &  # 二面的时间允许为空
                         models.Q(second_interview_result=0) &  # 二面的结果默认为 0 （待安排）
                         models.Q(second_unpass_reason=''))  # 原因默认为空 （除非结果！= 1 ）
                ),
                name='%(app_label)s_%(class)s_second_only_after_first_pass'
            ),

            models.CheckConstraint(
                check=(
                        models.Q(second_interview_result=1) |
                        (models.Q(third_interviewer__isnull=True) &
                         models.Q(third_interview_date__isnull=True) &
                         models.Q(third_interview_result=0) &
                         models.Q(third_unpass_reason=''))
                ),
                name='%(app_label)s_%(class)s_third_only_after_second_pass'
            ),

            models.CheckConstraint(
                check=(
                        ~models.Q(first_interview_result=2) |
                        (models.Q(second_interviewer__isnull=True) &
                         models.Q(second_interview_date__isnull=True) &
                         models.Q(second_interview_result=0))
                ),
                name='%(app_label)s_%(class)s_first_fail_no_second'
            ),
            models.CheckConstraint(
                check=(
                        ~models.Q(second_interview_result=2) |
                        (models.Q(third_interviewer__isnull=True) &
                         models.Q(third_interview_date__isnull=True) &
                         models.Q(third_interview_result=0))
                ),
                name='%(app_label)s_%(class)s_second_fail_no_third'
            ),

            # 用来检查数据的合法性，例如:一面通过却填写了未通过原因 && 一面未通过却没有未通过原因
            # 二面和三面的逻辑同上

            models.CheckConstraint(
                check=(models.Q(first_unpass_reason='') |
                       models.Q(first_interview_result=2)),
                name='%(app_label)s_%(class)s_reason_need_first_fail'
            ),
            models.CheckConstraint(
                check=(models.Q(second_unpass_reason='') |
                       models.Q(second_interview_result=2)),
                name='%(app_label)s_%(class)s_reason_need_second_fail'
            ),
            models.CheckConstraint(
                check=(models.Q(third_unpass_reason='') |
                       models.Q(third_interview_result=2)),
                name='%(app_label)s_%(class)s_reason_need_third_fail'
            ),
        ]

    def clean(self):
        super().clean()  # 保留数据模型的数据验证逻辑和自己编写的逻辑
        from django.utils import timezone
        today = timezone.now().date()  # 获取当天时间

        # 1. 日期不能是过去
        for f in ('first', 'second', 'third'):
            date_val = getattr(self, f'{f}_interview_date')
            if date_val and date_val < today:
                raise ValidationError(
                    {f'{f}_interview_date': '面试日期不能早于今天'})

        if self.first_interview_result == 2:
            if self.second_interviewer or self.second_interview_date:
                raise ValidationError('Error:一面未通过')
            if self.third_interviewer or self.third_interview_date:
                raise ValidationError('Error:一面未通过')

        if self.second_interview_result == 2:
            if self.third_interviewer or self.third_interview_date:
                raise ValidationError('Error:二面未通过')

        # 3. 原因与结果一致性
        for idx, f in enumerate(('first', 'second', 'third'), 1):
            result = getattr(self, f'{f}_interview_result')
            reason = getattr(self, f'{f}_unpass_reason')
            if result != 2 and reason:
                raise ValidationError(
                    f'{idx}面通过或待面试时，不需要填写未通过原因')

        # 4. 刷新最终状态
        self.update_final_status()

    def update_final_status(self):  # 更新最终状态
        """
       如果 2 存在 一面 、二面、三面 之中 则代表未通过 最终状态为 2
       如果 1 一面、二面、三面 为 1 则最终状态为 1
       否则为 0 待处理
        """
        if 2 in (self.first_interview_result, self.second_interview_result, self.third_interview_result):
            self.final_status = 2
        elif all(r == 1 for r in (self.first_interview_result,
                                  self.second_interview_result,
                                  self.third_interview_result)):
            self.final_status = 1

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        # 先刷新状态再校验，最后保存数据
        self.update_final_status()
        self.full_clean()  # 会调用 clean()
        super().save(force_insert, force_update, *args, **kwargs)

    def __str__(self):
        return f'{self.name} - {self.apply_position}'
